﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading.Tasks;
using ProgNetComponents.Classes;
using System.IO;

namespace ProgNetComponentsDx.Chat
{
    public class ChatClient :IDisposable
    {
        TcpClient clientSocket;
        NetworkStream serverStream;
        public event ChatMessageHandler MessageReceived;

        private string host;
        private int port;
        
        public ChatClient(string host, int port)
        {
            this.host = host;
            this.port = port;
        }
        ObjectHolder<bool> exited = new ObjectHolder<bool>(true);
        private bool isDisposed;

        public void Start()
        {
            if (exited.Value)
            {
                clientSocket = new TcpClient();
                clientSocket.Connect(host, port);
                exited.Value = false;
                isDisposed = false;
                ObjectHolder<TcpClient, ObjectHolder<bool>> obj = new ObjectHolder<TcpClient, ObjectHolder<bool>>();
                obj.Value1 = clientSocket;
                obj.Value2 = exited;

                Task tChecConnected = Task.Factory.StartNew(CheckConnection, obj);
                serverStream = clientSocket.GetStream();
                Task t = Task.Factory.StartNew(ListenForMessages);
            }
        }
        public void StartOrWait()
        {
            if (exited.Value)
            {
                isDisposed = false;
                Task t = Task.Factory.StartNew(StartOrWaitTask);
            }
        }
        private void StartOrWaitTask()
        {
            while (!isDisposed)
            {
                if (exited.Value)
                {
                    try
                    {
                        Start();
                    }
                    catch { }
                }
                else
                {
                    System.Threading.Thread.Sleep(100);
                }
            }
        }

        public virtual void Stop()
        {
            try
            {
                SendMessageToServer(ChatMessages.UserDisconnected, string.Empty);
            }
            catch { }
            try
            {
                exited.Value = true;
                isDisposed = true;
                clientSocket.Close();
            }
            catch { }
        }
        protected void FireMessageReceived(ChatMessageInfo info)
        {
            if (MessageReceived != null)
                MessageReceived(this, new ChatMessageEventArgs(info));
        }
        protected virtual void OnMessageReceived(ChatMessageInfo info)
        {
            FireMessageReceived(info);
        }
        private void ListenForMessages()
        {
            try
            {
                while (!exited.Value)
                {
                    ChatMessageInfo info = new ChatMessageInfo();
                    serverStream.ReceiveMessage(exited, info);
                    OnMessageReceived(info);
                }
                OnClientClose();
                this.clientSocket.Close();
            }
            catch
            {
                exited.Value = true;
                OnClientClose();
            }
        }
        protected virtual void OnClientClose()
        {

        }
        public void SendMessage(ChatMessageInfo info)
        {
            if (!isDisposed && !exited.Value)
                serverStream.SendMessage(info);
        }
        public void SendMessage(string type, string to, string msg)
        {
            ChatMessageInfo info = new ChatMessageInfo();
            info.Type = type;
            info.User = to;
            info.Message = msg;
            SendMessage(info);
        }
        public void SendMessage(string to, string msg)
        {
            SendMessage(ChatMessages.StringMsg, to, msg);
        }
        public void SendMessageToServer(string type, string msg)
        {
            ChatMessageInfo info = new ChatMessageInfo();
            info.User = string.Empty;
            info.Type = type;
            info.Message = msg;
            SendMessage(info);
        }
        public void SendFile(string fileName, string to)
        {
            SendFileTask f = new SendFileTask();
            f.FileName = fileName;
            f.To = to;
            f.Client = this;
            f.SendFile();
        }

        private class SendFileTask
        {
            public string FileName { get; set; }
            public string To { get; set; }
            public ChatClient Client { get; set; }
            public void SendFile()
            {
                Task t = Task.Factory.StartNew(SendFileT);
            }
            private void SendFileT()
            {
                try
                {
                    IList<byte> list = System.IO.File.ReadAllBytes(FileName).ToList();

                    FileInfo fi = new FileInfo(FileName);
                    using (var str = fi.OpenRead())
                    {
                        int i = 0;
                        int part = 4096;
                        byte[] bytes = new byte[part];
                        string uniqueId = "#" + FileName + "#" + Path.GetRandomFileName() + "#";
                        do
                        {
                            if (!Client.isDisposed && !Client.exited.Value)
                            {
                                i = str.Read(bytes, 0, part);

                                ChatMessageInfo info = new ChatMessageInfo();
                                info.Type = ChatMessages.File;
                                info.User = To;
                                info.Message = uniqueId;
                                if (i != part)
                                    info.Type = ChatMessages.FileEnd;

                                if (i == part)
                                    info.FileBytes = bytes;
                                else
                                    info.FileBytes = bytes.Take(i).ToArray();

                                Client.serverStream.SendMessage(info);
                            }
                        } while (i == part);
                    }
                }
                catch { }
            }
        }
        

        public void Dispose()
        {
            isDisposed = true;
            exited.Value = true;
            Stop();
        }
        protected virtual bool ShouldSendBeep()
        {
            return true;
        }
        private void CheckConnection(object obj)
        {
            ObjectHolder<TcpClient, ObjectHolder<bool>> o = obj as ObjectHolder<TcpClient, ObjectHolder<bool>>;
            if (o != null)
            {
                int i = 0;
                ChatMessageInfo info = new ChatMessageInfo();
                info.Message = string.Empty;
                info.Type = ChatMessages.Beep;

                while (!o.Value2.Value)
                {
                    System.Threading.Thread.Sleep(50);
                    if (!o.Value1.Connected)
                    {
                        o.Value2.Value = true;
                    }
                    else
                    {
                        try
                        {
                            i++;
                            if (i == 20)
                            {
                                i = 0;
                                if (ShouldSendBeep())
                                {
                                    o.Value1.GetStream().SendMessage(info);
                                }
                            }
                        }
                        catch
                        {
                            o.Value2.Value = true;
                        }
                    }
                }
            }
        }
    }

    public class MyChatClient : ChatClient
    {
        int nickNo = 0;
        public override void Stop()
        {
            base.Stop();
            nickNo = 0;
            Nick = string.Empty;
        }
        public MyChatClient(string host, int port) : base(host, port) { }
        public string Nick { get; private set; }

        protected override void OnMessageReceived(ChatMessageInfo info)
        {
            switch (info.Type)
            {
                case ChatMessages.GetNick:
                    Nick = System.Environment.UserName + (nickNo > 0 ? nickNo.ToString() : "");
                    SendMessageToServer(ChatMessages.GetNick, Nick);
                    nickNo++;
                    break;
                case ChatMessages.UserDisconnected:
                    info.Message = "** User [" + info.Message + "] Is Disconnected **";
                    info.User = string.Empty;
                    info.Type = ChatMessages.StringMsg;
                    break;
            }
            base.OnMessageReceived(info);
        }
    }

    public delegate void ChatMessageHandler(object sender, ChatMessageEventArgs e);

    public class ChatMessageEventArgs : EventArgs
    {
        public ChatMessageEventArgs(ChatMessageInfo info)
        {
            Info = info;
        }

        public ChatMessageInfo Info { get; private set; }
    }
}
